import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Random;


public class Question1 {

	protected String EXAMPLES_FILE = "oneperdigit.txt";
	protected float noiseProb = 0.1f;
	protected OutputGraphs grapic;
	protected Random random = new Random();
	
	
	public Question1(OutputGraphs graphs) {
		grapic=graphs;
		
	}

	public void run() throws IOException {
		ExamplesInputReader reader = new ExamplesInputReader();
		ArrayList<Example> allExmples = reader.getExamples(EXAMPLES_FILE);
		//creating the the power set of all the examples 2^n combinations
		Collection<ArrayList<Example>> powerSet = getAllPowerSet(allExmples);
		QuestionData data[]=new QuestionData[Constants.TOTAL_NUMBER_OF_DIGITS];
		for (int i = 1; i <= Constants.TOTAL_NUMBER_OF_DIGITS; i++) {
			//get all the sets from the size i:
			Collection<ArrayList<Example>> subset =chooseExamplesSubsetBySize(i,powerSet);
			//running over all the sets
			data[i-1] =new QuestionData();
			for (ArrayList<Example> exampleSet : subset) {
				
				int numberThatLearned=checkNetwork(exampleSet);
				data[i-1].newNumber(numberThatLearned);
			}
			System.out.println(" i :"+i+" max: "+data[i-1].getMax()+ " avg= "+data[i-1].getAvg());
		}
		grapic.questionOne(data);
				
		
	}

	/**
	 * given a set of examples this method construct clone few examples form the
	 * the given set of examples and flip every few bytes.
	 * 
	 * then , creates an hopfiled network based on the example set
	 * and tries to fix the examples with noise  
	 * 
	 * @param exampleSet
	 * @return
	 */
	protected int checkNetwork(ArrayList<Example> exampleSet) {
		Collection<Example> exampelsWithNoise = new LinkedList<Example>();
		//building the noise examples
		for (Example example : exampleSet) {
			for (int i = 0; i < Constants.SIZE_OF_CHANGED_EXAMPLES_Q1; i++) {
				exampelsWithNoise.add(getExampleWithChanges(example,example.digit));
			}
		}
		//learning set: the real examples:
		HopfieldNetwork hn = new HopfieldNetwork(exampleSet);
		int numberCorrect=0;
		int indexInExampleSet=0;
		int index=0;
		boolean test[]=new boolean[exampleSet.size()];
		for (Example example : exampelsWithNoise) {
			//new digit in the noise examples
			if (index==Constants.SIZE_OF_CHANGED_EXAMPLES_Q1)
			{
				indexInExampleSet+=1;
				index=0;
				if(numberCorrect>=0.9*Constants.SIZE_OF_CHANGED_EXAMPLES_Q1)
					test[indexInExampleSet]=true;
				else
					test[indexInExampleSet]=false;
				
				numberCorrect=0;
			}
			
			Example returnEx=hn.reconstructExample(example);
			
			if(exampleSet.get(indexInExampleSet).compareTo(returnEx)==0)
				numberCorrect++;
			index++;
		}
		
		//new digit in the noise examples
		if (index==Constants.SIZE_OF_CHANGED_EXAMPLES_Q1)
		{
			indexInExampleSet+=1;
			index=0;
			if(numberCorrect>=0.9*Constants.SIZE_OF_CHANGED_EXAMPLES_Q1)
				test[indexInExampleSet-1]=true;
			else
				test[indexInExampleSet-1]=false;
			
			numberCorrect=0;
		}
		int counter=0;
		for(int i=0; i<test.length; i++)
			if(test[i]==true)
				counter++;
		return counter;
	}

	/**
	 * search all the power set for sets with size i
	 * 
	 * @param i
	 * @param powerSet
	 * @return
	 */
	protected Collection<ArrayList<Example>> chooseExamplesSubsetBySize(int i,
			Collection<ArrayList<Example>> powerSet) {
		LinkedList<ArrayList<Example>> subsets = new LinkedList<ArrayList<Example>>();
		for (ArrayList<Example> set : powerSet) {
			if (set.size()==i)
				subsets.add(set);
		}
		return subsets;
	}

	/**
	 * Construct all the power set
	 * 
	 * @param allExmples
	 * @return
	 */
	protected Collection<ArrayList<Example>> getAllPowerSet(
			ArrayList<Example> allExmples) {
		LinkedList<ArrayList<Example>> subsets = new LinkedList<ArrayList<Example>>();
		subsets.add(new ArrayList<Example>());
		for (Example ex :allExmples) {
			LinkedList<ArrayList<Example>> newSubsets = new LinkedList<ArrayList<Example>>();
			for (ArrayList<Example> arrayList : subsets) {
			
					newSubsets.add(arrayList);
				
				ArrayList<Example> subset =(ArrayList<Example>)arrayList.clone();
				subset.add(ex);
				newSubsets.add(subset);
				
			}
			subsets= newSubsets;
		}
		return subsets;
	}




	/**
	 * This method adds noise to the example
	 * 
	 * @param example
	 * @param digit
	 * @return
	 */
	protected Example getExampleWithChanges(Example example,int digit) {
		Example e = new Example(example.byteArray.clone());
		for (int i = 0; i < e.byteArray.length; i++) {
			if (random.nextDouble()<=noiseProb){
				if(e.byteArray[i]==Constants.BIT_ON)
					e.byteArray[i]=Constants.BIT_OFF;
				else
					e.byteArray[i]=Constants.BIT_ON;
			}
				
		}
		e.digit = digit;
		return e;
	}
}
